﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using DARE.Effects.RenderEffect;

namespace DARE
{
    public abstract class AModel : ARenderable
    {

    #region identifiers

        public enum EFadingState { NONE, IN, END, OUT };
        public enum EHighlightState { NONE, IN, END, OUT };

    #endregion

    #region fields

        [ContentSerializer]
        protected CLModel m_model;

        private static BoundingBox zeroBoundingBox = new BoundingBox(Vector3.Zero, Vector3.Zero);

        protected Matrix m_orientation = Matrix.Identity;
        protected Vector3 m_scale = Vector3.One;
        protected Vector3 m_offset = Vector3.Zero;
        private Matrix m_relative = Matrix.Identity;
        private bool m_relativeChanged;
        protected bool m_show = true;
        protected CEffect m_effect;
        protected bool m_isTransparent;
        protected float m_transparency;
        private RasterizerState m_RasterizerState;
        private bool m_needUpdateRasterizer;

        private float m_fadingSpeedIn;
        private float m_fadingSpeedOut;
        private float m_fadingValue;
        private EFadingState m_fadingState = EFadingState.NONE;
        private bool m_fadeCalled;
        private float m_saveTransparency;
        private bool m_wasTransparent;

        private float m_highlightSpeedIn;
        private float m_highlightSpeedOut;
        private float m_highlightValue;
        private float m_startEmissiveIntensity;
        private Color m_startEmissiveColor;
        private EHighlightState m_highlightState = EHighlightState.NONE;
        private bool m_highlightCalled;

        protected BoundingBox m_maxBox;

    #endregion

    #region properties

        public BoundingBox Box
        {
            get
            {
                //if (m_maxBox != zeroBoundingBox)
                //    return m_maxBox;
                m_maxBox = GetMinBoundingBox();
                return m_maxBox;
            }
        }

        public FillMode FillMode
        {
            get { return m_RasterizerState.FillMode; }
            set
            {
                if (value != m_RasterizerState.FillMode)
                {
                    CullMode cullMode = m_RasterizerState.CullMode;

                    m_RasterizerState = new RasterizerState();
                    m_RasterizerState.FillMode = value;
                    m_RasterizerState.CullMode = cullMode;
                    m_needUpdateRasterizer = true;
                }
            }
        }

        public CullMode CullMode
        {
            get { return m_RasterizerState.CullMode; }
            set
            {
                if (value != m_RasterizerState.CullMode)
                {
                    FillMode fillMode = m_RasterizerState.FillMode;

                    m_RasterizerState = new RasterizerState();
                    m_RasterizerState.FillMode = fillMode;
                    m_RasterizerState.CullMode = value;
                    m_needUpdateRasterizer = true;
                }
            }
        }

        public float FadingValue
        {
            get { return m_fadingValue; }
            set { m_fadingValue = value; }
        }

        /// <summary>
        /// Get the fading speed is the average of fade in and fade out speeds.
        /// Default value is 1.
        /// </summary>
        public float FadingSpeed
        {
            get { return (m_fadingSpeedIn + m_fadingSpeedOut) / 2; }
            set
            {
                if (value >= 0)
                {
                    m_fadingSpeedIn = value;
                    m_fadingSpeedOut = value;
                }
            }
        }

        public float FadingSpeedIn
        {
            get { return m_fadingSpeedIn; }
            set
            {
                if (value >= 0)
                    m_fadingSpeedIn = value;
            }
        }

        public float FadingSpeedOut
        {
            get { return m_fadingSpeedOut; }
            set
            {
                if (value >= 0)
                    m_fadingSpeedOut = value;
            }
        }


        public float HighlightValue
        {
            get { return m_highlightValue; }
            set { m_highlightValue = value; }
        }

        /// <summary>
        /// Get the fading speed is the average of fade in and fade out speeds.
        /// Default value is 1.
        /// </summary>
        public float HighlightSpeed
        {
            get { return (m_highlightSpeedIn + m_highlightSpeedOut) / 2; }
            set
            {
                if (value >= 0)
                {
                    m_highlightSpeedIn = value;
                    m_highlightSpeedOut = value;
                }
            }
        }

        public float HighlightSpeedIn
        {
            get { return m_highlightSpeedIn; }
            set
            {
                if (value >= 0)
                    m_highlightSpeedIn = value;
            }
        }

        public float HighlightSpeedOut
        {
            get { return m_highlightSpeedOut; }
            set
            {
                if (value >= 0)
                    m_highlightSpeedOut = value;
            }
        }

        /// <summary>
        /// Set if the model is transparent or not.
        /// </summary>
        public bool IsTransparent
        {
            get { return m_isTransparent; }
            set
            {
                if (value != m_isTransparent)
                {
                    m_isTransparent = value;
                    Effect.Alpha = m_isTransparent ? m_transparency : 1;
                    if (m_node != null)
                        m_node.Order = m_isTransparent ? EOrder.BACKTOFRONT : EOrder.REGULAR;
                }
            }
        }

        /// <summary>
        /// 1 Not transparent
        /// 0 Totally transparent
        /// </summary>
        public float Transparency
        {
            get { return m_transparency; }
            set
            {
                if (value != m_transparency)
                {
                    m_transparency = value;
                    if (m_transparency > 1)
                        m_transparency = 1;
                    else if (m_transparency < 0)
                        m_transparency = 0;
                    if (m_isTransparent == true)
                        Effect.Alpha = m_transparency;
                }
            }
        }

        public override string Name
        {
            get { return base.Name; }
            set { base.Name = value; }
        }

        public virtual CEffect Effect
        {
            get { return m_effect; }
            set { m_effect = value; }
        }

        public Matrix Relative
        {
            get
            {
                if (m_relativeChanged)
                {
                    m_relative = Matrix.CreateScale(m_scale) * m_orientation * Matrix.CreateTranslation(m_offset);
                    m_relativeChanged = false;
                }
                return m_relative;
            }
        }

        public Model Model
        {
            get
            {
                if (m_model == null)
                    return null;
                return m_model.Data;
            }
        }

        public bool Show
        {
            get { return m_show; }
            set
            {
                m_show = value;
                if (!value)
                    DetachFromRenderer();
                else
                    AttachToRenderer();
            }
        }

        public Vector3 Scale
        {
            get { return m_scale; }
            set
            {
                m_scale = value;
                m_relativeChanged = true;
            }
        }

        public Vector3 Offset
        {
            get { return m_offset; }
            set
            {
                m_offset = value;
                m_relativeChanged = true;
            }
        }

        public Matrix Orientation
        {
            get { return m_orientation; }
            set
            {
                m_orientation = value;
                m_relativeChanged = true;
            }
        }

        public EFadingState FadingState
        {
            get { return m_fadingState; }
        }

    #endregion

    #region attach/detach

        public AModel AttachToEntity(CEntity entity)
        {
            entity.AttachModel(this);
            return this;
        }

        public AModel DetachFromEntity()
        {
            m_entity.DetachModel();
            return this;
        }

        internal virtual void AttachToRenderer()
        {
            //var _oldBoxLength = (base.Entity.Node.AABB.Max - base.Entity.Node.AABB.Min).Length();
            //var _newLenght = (this.Box.Max - this.Box.Min).Length();
            //if (_newLenght > _oldBoxLength)
            //    base.Entity.Node.AABB = this.Box;

        }

        internal virtual void DetachFromRenderer()
        {
        }

    #endregion

    #region ctor

        public AModel()
        {
            Initialize();
        }

        public void Initialize()
        {
            m_effect = new CEffect();
            m_isTransparent = false;
            Transparency = 1;
            m_RasterizerState = CDare.Instance.GraphicsDevice.RasterizerState;
            m_needUpdateRasterizer = false;
            FadingValue = 0.5f;
            FadingSpeed = 1;
            HighlightValue = 0.5f;
            HighlightSpeed = 1;
        }

    #endregion

    #region methods

        protected abstract BoundingBox GetMinBoundingBox();

        public void Fade()
        {
            if (m_fadingState == EFadingState.NONE)
            {
                m_fadingState = EFadingState.IN;
                m_saveTransparency = Transparency;
                if (!IsTransparent)
                    m_wasTransparent = false;
                else
                    m_wasTransparent = true;
            }
            else if (m_fadingState == EFadingState.OUT)
                m_fadingState = EFadingState.IN;
            m_fadeCalled = true;
        }

        public void Highlight()
        {
            if (m_highlightState == EHighlightState.NONE)
            {
                m_startEmissiveIntensity = Effect.EmissiveLightIntensity;
                m_startEmissiveColor = Effect.EmissiveLightColor;
                m_highlightState = EHighlightState.IN;
            }

        }

        public virtual AModel Clone()
        {
            return this;
        }

        protected void CopyPropertiesTo(AModel otherModel)
        {
            otherModel.Orientation = m_orientation;
            otherModel.Scale = m_scale;
            otherModel.Offset = m_offset;
            otherModel.Show = m_show;
        }

        public override void Update(GameTime gameTime)
        {
            UpdateFade(gameTime);
            UpdateHighlight(gameTime);
        }

        public void UpdateFade(GameTime gameTime)
        {
            float time = CDare.Instance.Config.Time.FixedTimeSinceLastFrame / 1000.0f;

            switch (m_fadingState)
            {
                case EFadingState.NONE:
                    break;

                case EFadingState.IN:
                    float actualTransparency = Transparency;

                    if (!IsTransparent)
                        IsTransparent = true;
                    if (actualTransparency > m_fadingValue)
                    {
                        actualTransparency -= m_fadingSpeedIn * time;
                        if (actualTransparency < m_fadingValue)
                        {
                            actualTransparency = m_fadingValue;
                            m_fadingState = EFadingState.END;
                        }
                        Transparency = actualTransparency;
                    }
                    break;

                case EFadingState.END:
                    if (!m_fadeCalled)
                    {
                        m_fadingState = EFadingState.OUT;
                        Update(gameTime);
                    }
                    break;

                case EFadingState.OUT:
                    float val = m_wasTransparent ? m_saveTransparency : 1;

                    actualTransparency = Transparency;
                    if (actualTransparency < val)
                    {
                        actualTransparency += m_fadingSpeedOut * time;
                        if (actualTransparency > val)
                        {
                            actualTransparency = val;
                            m_fadingState = EFadingState.NONE;
                            if (!m_wasTransparent)
                                IsTransparent = false;
                        }
                        Transparency = IsTransparent ? actualTransparency : m_saveTransparency;
                    }
                    break;
            }
            m_fadeCalled = false;
        }

        public void UpdateHighlight(GameTime gameTime)
        {
            float time = CDare.Instance.Config.Time.FixedTimeSinceLastFrame / 1000.0f;

            switch (m_highlightState)
            {
                case EHighlightState.NONE:
                    break;

                case EHighlightState.IN:
                    Effect.EmissiveLightIntensity += m_highlightSpeedIn * time;

                    if (Effect.EmissiveLightIntensity > m_highlightValue)
                    {
                        Effect.EmissiveLightIntensity = m_highlightValue;
                        m_highlightState = EHighlightState.OUT;
                    }
                    Effect.EmissiveLightColor = Color.Lerp(m_startEmissiveColor, Color.White, Effect.EmissiveLightIntensity / m_highlightValue);
                    break;

                case EHighlightState.END:
                    break;

                case EHighlightState.OUT:
                    Effect.EmissiveLightIntensity -= m_highlightSpeedOut * time;

                    if (Effect.EmissiveLightIntensity < m_startEmissiveIntensity)
                    {
                        Effect.EmissiveLightIntensity = m_startEmissiveIntensity;
                        m_highlightState = EHighlightState.NONE;
                    }
                    Effect.EmissiveLightColor = Color.Lerp(Color.White, m_startEmissiveColor, Effect.EmissiveLightIntensity / m_highlightValue);
                    break;
            }
            m_highlightCalled = false;
        }

        public override void Draw(GameTime gameTime)
        {
            CDare.Instance.GraphicsDevice.RasterizerState = m_RasterizerState;
        }

    #endregion

    }
}
